﻿// Type to create AJAX requests to the server
define([
    'Wjs/Wjs'
],
function () {
    'use strict';

    return Wjs.defineOnce({

        type: 'HttpRequest',

        internal: {

            construct: function () {

                this.headers = {};

                this.setMethod('GET');

                this.setAsynchronous(true);
            }
        },

        setMethod: function (method) {

            this.method = method;

            return this;
        },

        getMethod: function () {

            return this.method;
        },

        addHeader: function (name, value) {

            this.headers[name] = value;

            return this;
        },

        setUrl: function (url) {

            this.url = url;

            // Ensure the URL template and parameters are nullified

            this.urlTemplate = null;

            this.urlTemplateParameters = null;

            return this;
        },

        setUrlTemplate: function (urlTemplate, templateParameters) {

            this.urlTemplate = Wjs.compile(urlTemplate);

            this.urlTemplateParameters = templateParameters;

            // Ensure the non template URL is nullified
            this.url = null;

            return this;
        },

        setParameters: function (parameters) {

            this.parameters = parameters;

            return this;
        },

        // Set the timeout in milliseconds
        setTimeout: function (timeout) {

            this.timeout = timeout;

            return this;
        },

        setAsynchronous: function (asynchronous) {

            this.asynchronous = asynchronous;

            return this;
        },

        // Ignores the returned content typ drom the server and uses the one set in there
        setOverrideMimeType: function (mimeType) {

            this.overrideMimeType = mimeType;

            return this;
        },

        onSuccess: function (fcn) {

            this.successHandler = fcn; // Do not chain since we want to replace the default success handler

            return this;
        },

        // If a callback was provided to the send method, then call it with the response
        successHandler: function (response, callback) {

            if (callback) {

                callback(response);
            }
        },

        onNotFound: function (fcn) {

            this.notFoundHandler = Wjs.chain(this.notFoundHandler, fcn);

            return this;
        },

        onTimeout: function (fcn) {

            this.timeoutHandler = fcn; // Do not chain since we want to replace the default timeout handler

            return this;
        },

        // Default tiemout handler.  It can be replaced by using onTimeout although it is a kind of error we handle it separately to simplify error handling
        timeoutHandler: function () {

            Wjs.alert()
                .setTitle('Server timeout')
                .setContent('Request to URL: ' + this.url + ' timed out. Timeout set to: ' + this.timeout + ' milliseconds')
                .show();
        },

        onError: function (fcn) {

            this.errorHandler = fcn; // Do not chain since we want to replace the default error handler

            return this;
        },

        // The default error handler. It can be replaced by using onError
        errorHandler: function (response) {

            Wjs.alert()
               .setTitle('Error')
               .setContent(response.error || response.description)
               .show();
        },

        onComplete: function (fcn) {

            this.completeHandler = Wjs.chain(this.completeHandler, fcn);

            return this;
        },

        // Sends a request to the server, calling a callback on success
        send: function (callback) {
            var me = this,
                url = this.buildUrl(),
                request = new XMLHttpRequest(),
                data = this.method.toUpperCase() == 'GET' ? null : this.buildParametersString(),
                accountManager;

            request.onreadystatechange = function () {

                try {

                    if (request.readyState === 4) {

                        me.process(request, callback);
                    }
                }
                catch (error) {

                    console.log(error.message || error + '\n' + error.stack);

                    var response = {
                        error: error
                    };

                    if (me.errorHandler) {

                        me.errorHandler(response);
                    }

                    if (me.completeHandler) { // Called on success or on error

                        me.completeHandler(response);
                    }
                }
            };

            request.open(me.method, url, me.asynchronous);

            if (me.timeout) {

                request.timeout = me.timeout; // Timeout is in milliseconds

                request.ontimeout = me.timeoutHandler.bind(me);
            }

            if (me.overrideMimeType) {

                request.overrideMimeType(me.overrideMimeType);
            }

            // Add the authorization header if the user is logged in
            accountManager = Wjs.getType('accountManager', true); // Optional

            if (accountManager && accountManager.isLoggedIn()) {

                accountManager.addAuthorization(me.headers);
            }

            // Set the headers
            for (var name in me.headers) {

                request.setRequestHeader(name, me.headers[name]);
            }

            request.send(data);

            if (!me.asynchronous) { // The execution is blocked until the response returned from the server

                me.process(request, callback);
            }

            return {

                abort: function () {

                    request.abort();
                }
            };
        },

        buildUrl: function () {

            if (this.urlTemplate) { // If there is a template then use it

                return this.urlTemplate(this.urlTemplateParameters);
            }

            if (this.method.toUpperCase() == 'GET' && this.parameters) { // Build parameter string and append it to the url

                return this.url + '?' + this.buildParametersString();
            }

            // Any other method, do not prepend any parameters
            return this.url;
        },

        buildParametersString: function () {
            var parameters = this.parameters,
                output = '',
                first = true; // Needed to append the & to the other (not first) parameters

            if (!parameters) {

                return '';
            }

            if (Wjs.isString(parameters) ||
                parameters instanceof FormData) {

                return parameters; // Assume the parameters are provided as a string
            }

            // From url-encoded
            // TODO: Support other encodings
            for (var name in parameters) {

                if (first) {

                    output += encodeURIComponent(name) + '=' + encodeURIComponent(parameters[name]);

                    first = false;
                }
                else {

                    output += '&' + encodeURIComponent(name) + '=' + encodeURIComponent(parameters[name]);
                }

            }

            return output;

        },

        process: function (request, callback) {
            var response;

            if (request.status === 0) {

                return; // Timeout
            }

            response = {
                status: request.status,
                description: request.statusText,
                headers: this.parseHeaders(request.getAllResponseHeaders()),
                text: request.responseText,
                xml: request.responseXML
            };

            console.log(response);

            if ((request.status >= 200 && request.status < 300) || request.status == 304) {

                if (this.successHandler) {

                    this.successHandler(response, callback); // Pass the callback to the success handler
                }
            }
            else if (response.status == 404) { // Nowadays with RESTful it might be an error or not

                if (this.notFoundHandler) {

                    this.notFoundHandler(response);
                }
                else if (this.errorHandler) {

                    this.errorHandler(response);
                }
            }
            else { // Error from server

                if (this.errorHandler) {

                    this.errorHandler(response);
                }
            }

            if (this.completeHandler) { // Called on success or on error

                this.completeHandler(response);
            }
        },

        // Headers come from the server as a string with multiple lines, one for each header, so this function converst it to an object
        parseHeaders: function (headersString) {
            var headers = {},
                headerLines = headersString.split('\n'),
                parts;

            headerLines.forEach(function (headerLine) {
                parts = headerLine.split(': ');
                headers[parts[0]] = parts[1];
            });

            return headers;
        }

    });
});